<?
/**
 * Copyright 2007 Melange.
 *
 * This file is part of PHP-AAS.
 *
 * PHP-AAS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * PHP-AAS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PHP-AAS; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @category    Melange
 * @package     php-aas
 * @subpackage  deploy
 * @copyright   Copyright (c) 2007 Jeroen Simons. All rights reserved
 * @author      Jeroen Simons <jeroen@melange.nl>
 * @link        http://www.melange.nl/
 *
 */

/**
 * Representation of a security constraint element for a web application,
 * as represented in a <code>&lt;security-constraint&gt;</code> element in the
 * deployment descriptor.
 * <p>
 * <b>WARNING</b>:  It is assumed that instances of this class will be created
 * and modified only within the context of a single thread, before the instance
 * is made visible to the remainder of the application.  After that, only read
 * access is expected.  Therefore, none of the read and write access within
 * this class is synchronized.
 */

final class SecurityConstraint {


    // ----------------------------------------------------------- Constructors


    /**
     * Construct a new security constraint instance with default values.
     */
    public function __construct() {

    }


    // ----------------------------------------------------- Instance Variables


    /**
     * Was the "all roles" wildcard included in the authorization constraints
     * for this security constraint?
     */
    private $allRoles = false;


    /**
     * Was an authorization constraint included in this security constraint?
     * This is necessary to distinguish the case where an auth-constraint with
     * no roles (signifying no direct access at all) was requested, versus
     * a lack of auth-constraint which implies no access control checking.
     */
    private $authConstraint = false;


    /**
     * The set of roles permitted to access resources protected by this
     * security constraint.
     */
    private $authRoles = array();


    /**
     * The set of web resource collections protected by this security
     * constraint.
     */
    private $collections = array();


    /**
     * The user data constraint for this security constraint.  Must be NONE,
     * INTEGRAL, or CONFIDENTIAL.
     */
    private $userConstraint = "NONE";


    // ------------------------------------------------------------- Properties


    /**
     * Was the "all roles" wildcard included in this authentication
     * constraint?
     */
    public function getAllRoles() {

        return ($this->allRoles);

    }


    /**
     * Return the authorization constraint present flag for this security
     * constraint.
     */
    public function getAuthConstraint() {

        return ($this->authConstraint);

    }


    /**
     * Set the authorization constraint present flag for this security
     * constraint.
     */
    public function setAuthConstraint($authConstraint) {

        $this->authConstraint = $authConstraint;

    }


    /**
     * Return the user data constraint for this security constraint.
     */
    public function getUserConstraint() {

        return ($this->userConstraint);

    }


    /**
     * Set the user data constraint for this security constraint.
     *
     * @param userConstraint The new user data constraint
     */
    public function setUserConstraint($userConstraint) {

        if ($userConstraint !== null)
            $this->userConstraint = $userConstraint;

    }


    // --------------------------------------------------------- Public Methods


    /**
     * Add an authorization role, which is a role name that will be
     * permitted access to the resources protected by this security constraint.
     *
     * @param authRole Role name to be added
     */
    public function addAuthRole($authRole) {

        if ($authRole === null)
            return;
        if ("*" == $authRole) {
            $this->allRoles = true;
            return;
        }
        $this->authRoles[] = $authRole;
        $this->authConstraint = true;
    }


    /**
     * Add a new web resource collection to those protected by this
     * security constraint.
     *
     * @param collection The new web resource collection
     */
    public function addCollection(SecurityCollection $collection) {

        if ($collection === null)
            return;

        $this->collections[] = $collection;

    }


    /**
     * Return <code>true</code> if the specified role is permitted access to
     * the resources protected by this security constraint.
     *
     * @param role Role name to be checked
     */
    public function findAuthRole($role) {

        if ($role === null)
            return (false);
        return in_array($role, $roles);
        
    }


    /**
     * Return the set of roles that are permitted access to the resources
     * protected by this security constraint.  If none have been defined,
     * a zero-length array is returned (which implies that all authenticated
     * users are permitted access).
     */
    public function findAuthRoles() {

        return ($this->authRoles);

    }


    /**
     * Return the web resource collection for the specified name, if any;
     * otherwise, return <code>null</code>.
     *
     * @param name Web resource collection name to return
     */
    public function findCollection($name) {

        if ($name === null)
            return (null);
        foreach($this->collections as $collection)
            if($collection->getName() == $name)
                return $collection;
        return null;

    }


    /**
     * Return all of the web resource collections protected by this
     * security constraint.  If there are none, a zero-length array is
     * returned.
     */
    public function findCollections() {

        return ($this->collections);

    }


    /**
     * Return <code>true</code> if the specified context-relative URI (and
     * associated HTTP method) are protected by this security constraint.
     *
     * @param uri Context-relative URI to check
     * @param method Request method being used
     */
    public function included($uri, $method) {

        // We cannot match without a valid request method
        if ($method === null)
            return (false);

        // Check all of the collections included in this constraint
        for ($i = 0; $i < count($this->collections); $i++) {
            if (!$this->collections[$i]->findMethod($method))
                continue;
            $patterns = $this->collections[$i]->findPatterns();
            for ($j = 0; $j < count($patterns); $j++) {
                if ($this->matchPattern($uri, $patterns[$j]))
                    return (true);
            }
        }

        // No collection included in this constraint matches this request
        return (false);

    }


    /**
     * Remove the specified role from the set of roles permitted to access
     * the resources protected by this security constraint.
     *
     * @param authRole Role name to be removed
     */
    public function removeAuthRole($authRole) {

        if ($authRole === null)
            return;

        for ($i = 0; $i < count($this->authRoles); $i++) {
            if ($this->authRoles[$i] == $authRole) {
                unset($this->authRoles[$i]);
                return;
            }
        }

//        int $n = -1;
//        for ($i = 0; $i < count($this->authRoles); $i++) {
//            if ($this->authRoles[$i] == $authRole) {
//                $n = $i;
//                break;
//            }
//        }
//        if ($n >= 0) {
//            $j = 0;
//            $results = array();;
//            for ($i = 0; $i < count($this->authRoles); $i++) {
//                if ($i != $n)
//                    $results[$j++] = $this->authRoles[$i];
//            }
//            $this->authRoles = $results;
//        }

    }


    /**
     * Remove the specified web resource collection from those protected by
     * this security constraint.
     *
     * @param collection Web resource collection to be removed
     */
    public function removeCollection(SecurityCollection $collection) {

        if ($collection == null)
            return;

        for ($i = 0; $i < count($this->collections); $i++) {
            if ($this->collections[$i] == $collection) {
                unset($this->collections[$i]);
                return;
            }
        }

//        int n = -1;
//        for (int i = 0; i < collections.length; i++) {
//            if (collections[i].equals(collection)) {
//                n = i;
//                break;
//            }
//        }
//        if (n >= 0) {
//            int j = 0;
//            SecurityCollection results[] =
//                new SecurityCollection[collections.length - 1];
//            for (int i = 0; i < collections.length; i++) {
//                if (i != n)
//                    results[j++] = collections[i];
//            }
//            collections = results;
//        }

    }


    /**
     * Return a String representation of this security constraint.
     */
    public function __toString() {

        $sb = "SecurityConstraint[";
        for ($i = 0; $i < count($this->collections); $i++) {
            if ($i > 0)
                $sb . ", ";
            $sb .= $this->collections[$i]->getName();
        }
        $sb .= "]";
        return ($sb);

    }


    // -------------------------------------------------------- Private Methods


    /**
     * Does the specified request path match the specified URL pattern?
     * This method follows the same rules (in the same order) as those used
     * for mapping requests to servlets.
     *
     * @param path Context-relative request path to be checked
     *  (must start with '/')
     * @param pattern URL pattern to be compared against
     */
    private function matchPattern($path, $pattern) {

        // Normalize the argument strings
        if (($path === null) || (strlen($path) == 0))
            $path = "/";
        if (($pattern === null) || (strlen($pattern) == 0))
            $pattern = "/";

        // Check for exact match
        if ($path == $pattern)
            return (true);

        // Check for path prefix matching
        if (preg_match("/^\//", $pattern) && preg_match("/\/\*$/", $pattern)) {
            $pattern = substr($pattern, 0, strlen($pattern) - 2);
            if (strlen($pattern) == 0)
                return (true);  // "/*" is the same as "/"
            if (preg_match("/\/$/", $path))
                $path = substr($path, 0, strlen($path) - 1);
            while (true) {
                if ($pattern == $path)
                    return (true);
                $slash = strrpos($path, "/");
                if (!$slash || $slash === 0)
                    break;
                $path = substr($path, 0, $slash);
            }
            return (false);
        }

        // Check for suffix matching
        if (preg_match("/^\*\./", $pattern)) {
            $slash = strrpos($path, "/");
            $period = strrpos($path, ".");
            if (($slash >= 0) && ($period > $slash) &&
                preg_match("/".substr($pattern, 1)."/", $path)) {
                return (true);
            }
            return (false);
        }

        // Check for universal mapping
        if ($pattern == "/")
            return (true);

        return (false);

    }


}
?>